/**
 * @author harry
 * @date 2022/2/27/23:09
 */

package com.serviceOrchestration.service;

import com.alibaba.fastjson.JSON;
import com.serviceOrchestration.atomic.AtomicUnit;
import com.serviceOrchestration.atomic.AtomicUnitContext;
import com.serviceOrchestration.atomic.AtomicUnitInfo;
import com.serviceOrchestration.dao.ProductDao;
import com.serviceOrchestration.dao.PurchaseDao;
import com.serviceOrchestration.dao.UserDao;
import com.serviceOrchestration.exception.PurchaseException;
import com.serviceOrchestration.exception.ServerException;
import com.serviceOrchestration.factory.AtomicFactory;
import com.serviceOrchestration.factory.ProductFactory;
import com.serviceOrchestration.pojo.Product;
import com.serviceOrchestration.pojo.ProductClient;
import com.serviceOrchestration.pojo.PurchaseRecord;
import com.serviceOrchestration.pojo.User;
import com.serviceOrchestration.util.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class ProductService {

    private final UserDao userDao;
    private final ProductDao productDao;
    private final AtomicFactory atomicFactory;
    private final ProductFactory productFactory;
    private final PurchaseDao purchaseDao;

    private static final ConcurrentHashMap<String, Product> productCache = new ConcurrentHashMap<>();
    private static final ExecutorService databaseUpdater = Executors.newFixedThreadPool(1);

    @Autowired
    private ProductService(UserDao userDao, ProductDao productDao, AtomicFactory atomicFactory, ProductFactory productFactory, PurchaseDao purchaseDao) {
        this.userDao = userDao;
        this.productDao = productDao;
        this.atomicFactory = atomicFactory;
        this.productFactory = productFactory;
        this.purchaseDao = purchaseDao;
        initialCache();
    }

    /**
     * 在构造bean时就初始化缓存
     */
    private void initialCache() {
        List<Product> productList = productDao.selectAllProducts();
        for(Product p : productList) {
//            injectServices(p);
            productCache.put(p.getId(), p);
        }
    }

    @SuppressWarnings("unchecked")
    private void injectServices(Product product) {
        AtomicUnit unitChain = atomicFactory.makeChain(JSON.parseObject(product.getServiceJson(), List.class));
        product.setServices(unitChain);
    }

    public List<AtomicUnitInfo> getAtomicUnitsInfo() {
        return atomicFactory.getAtomicsInfo();
    }

    /**
     * 获取所有产品
     * @apiNote  目前可见的是可购买的产品
     */
    public List<ProductClient> getAllProducts() {
        ArrayList<ProductClient> products = new ArrayList<>();
        for(Product p : productCache.values()) {
            if(p.isAvailable()) {
                products.add(new ProductClient(p));
            }
        }
        return products;
    }

    /**
     * 管理员获取所有产品信息，包括可购买的和不可购买的
     */
    public List<Product> getProductList() {
        return new ArrayList<>(productCache.values());
    }

    /**
     * 获取单个产品
     */
    public Product getProduct(String id) {
        Product product = productCache.get(id);
        if(product == null) {
            throw new IllegalArgumentException("no such product");
        }
        return product;
    }

    /**
     * 创建新产品，产品状态为未审核 (available = false)
     * @apiNote 先注入产品参数 {@code properties}，再生成原子服务链和 json 串，最后更新数据库、缓存
     * @throws IllegalArgumentException 缺少参数、参数为 {@code null}、参数有误
     */
    public void createProduct(Map<String, Object> properties, List<Map<String, Object>> services, String role) {
        if(properties == null || services == null) {
            throw new IllegalArgumentException("param is null");
        }
        boolean productStatus = Role.SUP.equals(role);
        Product newProduct = productFactory.make(properties, productStatus);
        AtomicUnit serviceChain = atomicFactory.makeChain(services);
        if(newProduct == null || serviceChain == null) {
            throw new IllegalArgumentException("param error");
        }
        newProduct.setServices(serviceChain);
        newProduct.setServiceJson(JSON.toJSONString(services));

        productCache.put(newProduct.getId(), newProduct);
        databaseUpdater.submit(() -> productDao.insert(newProduct));
    }

    /**
     * 修改产品属性
     * @throws IllegalArgumentException 参数为 null、不正确的属性名
     * @apiNote 获得写锁、反射注入属性值、释放写锁、修改数据库
     */
    public void editProperties(String productId, Map<String, Object> modification) {
        if(productId == null || modification == null) {
            throw new IllegalArgumentException("param is null");
        }
        Product product = getProduct(productId);
        synchronized (product.getLock()) {
            for (String fieldName : modification.keySet()) {
                try {
                    Field field = Product.class.getDeclaredField(fieldName);
                    field.setAccessible(true);
                    field.set(product, modification.get(fieldName));
                } catch (NoSuchFieldException e) {
                    throw new IllegalArgumentException("no field called '" + fieldName + "'");
                } catch (IllegalAccessException e) {
                    throw new ServerException(e.getMessage());
                } // IllegalArgumentException 直接抛
            }
        }
        databaseUpdater.execute(() -> productDao.updateProperties(product));
    }

    /**
     * 修改产品的服务链
     * @apiNote 生成服务链、获取product、注入json、注入服务链
     * @throws IllegalArgumentException 参数为 null、参数有误不能构造原子服务链
     */
    public void editServiceChain(String productId, List<Map<String, Object>> services) {
        if(productId == null || services == null) {
            throw new IllegalArgumentException("param is null");
        }
        AtomicUnit unitChain = atomicFactory.makeChain(services);
        if(unitChain == null) {
            throw new IllegalArgumentException("can't construct services");
        }
        Product product = getProduct(productId);
        String serviceJson = JSON.toJSONString(services);
        synchronized (product.getLock()) {
            product.setServices(unitChain);
            product.setServiceJson(serviceJson);
        }
        databaseUpdater.submit(() -> productDao.updateServices(productId, serviceJson));
    }

    /**
     * 用户购买产品
     * @apiNote 获取产品和用户、检验账户余额、构造服务上下文、检查注入原子服务链、验证、交易信息写入数据库、从上下文取出信息返回；
     */
    public Map<String, Object> purchase(String userId, String productId, String amountStr) {
        if(productId == null || amountStr == null) {
            throw new IllegalArgumentException("param is null");
        }
        int amount;
        try {
            amount = Integer.parseInt(amountStr);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(e.getMessage());
        }

        Product product = getProduct(productId);
        User user = getUser(userId);
        // 先检验余额
        if (user.getBalance() < amount) {
            throw new PurchaseException("余额不足");
        }
        AtomicUnitContext context = createAtomicContext(user, product, amount);
        // 检查注入服务链
        if (product.getServices() == null) {
            synchronized (product.getLock()) {
                if (product.getServices() == null) {
                    injectServices(product);
                }
            }
        }
        // 验证
        if (!product.handle(context)) {
            throw new PurchaseException(context.getError());
        }
        // 减库存
        synchronized (product.getLock()) {
            int curSurplus = product.getSurplus();
            if (curSurplus == 0) {
                throw new PurchaseException("没有库存");
            }
            product.setSurplus(curSurplus - 1);
        }
        // 更新库存、余额、流水
        databaseUpdater.submit(() -> productDao.decreaseSurplus(productId));
        userDao.decreaseBalance(userId, amount);
        purchaseDao.insert(userId, productId, amount, new Date());
        return context.getReturnData();// 上下文数据中可能包含返回给客户端的信息，如预期利息
    }

    /**
     * 获取用户实体
     */
    private User getUser(String userId) {
        List<User> userList = userDao.selectById(userId);
        if(userList == null || userList.size() != 1) {
            throw new ServerException("user is null or duplicate");
        }
        return userList.get(0);
    }

    /**
     * 创建服务上下文
     * @apiNote 用户实体、购买金额、查数据库获得当日&总共额度剩余
     */
    private AtomicUnitContext createAtomicContext(User user, Product product, int amount) {
        AtomicUnitContext context = new AtomicUnitContext();
        context.addContext("user", user);
        context.addContext("amount", amount);
        Integer amountDaily = purchaseDao.selectAmountDaily(user.getId(), product.getId());
        Integer amountSum = purchaseDao.selectAmountSum(user.getId(), product.getId());
        context.addContext("amountDaily", amountDaily == null ? 0 : amountDaily);
        context.addContext("amountSum", amountSum == null ? 0 : amountSum);
        return context;
    }

    /**
     * 获取用户购买记录
     */
    public List<PurchaseRecord> getPurchaseHistory (String userId) {
        List<PurchaseRecord> records = purchaseDao.selectRecordByUserId(userId);
        if(records == null) {
            throw new ServerException("records is null");
        }
        return records;
    }

    public void approveProduct(String productId) {
        if (productId == null) {
            throw new IllegalArgumentException("param is null");
        }
        Product product = productCache.get(productId);
        if (product == null) {
            throw new IllegalArgumentException("param error");
        }
        synchronized (product.getLock()) {
            product.setAvailable(true);
        }
        databaseUpdater.submit(() -> productDao.updateStatus(productId, true));
    }
}
